#ifndef SWEGL_AXIS_ALIGNED_BOX_H
#define SWEGL_AXIS_ALIGNED_BOX_H

namespace swegl
{

class AABox
{
public:
	enum Corner
	{
		CornerFarLeftBottom,
		CornerFarLeftTop,
		CornerFarRightTop,
		CornerFarRightBottom,
		
		CornerNearRightBottom,
		CornerNearLeftBottom,
		CornerNearLeftTop,
		CornerNearRightTop,
	};
		
	// default contructor, do not initialize components
	AABox();
	AABox(const Vec3 &min, const Vec3 &max);

	AABox &setMinMax(const Vec3 &min, const Vec3 &max);
	AABox &setCenterHalfSize(const Vec3 &center, const Vec3 &halfSize);

	AABox(const AABox &rhs);
	AABox &operator = (const AABox &rhs);

	bool operator == (const AABox &rhs) const;
	bool operator != (const AABox &rhs) const;
	
	// center of this box
	Vec3 center() const;
	// half the diagonal of this box
	Vec3 halfSize() const;
	// diagonal of this box
	Vec3 size() const;
	// minimum corner of this box
	const Vec3 &minimum() const;
	Vec3 &minimum();
	// maximum corner of this box
	const Vec3 &maximum() const;
	Vec3 &maximum();
	// return a corner of this box
	Vec3 corner(Corner cornerIndex) const;
	
	AABox &translate(const Vec3 &translation);
	AABox &rotate(const Matrix3x3 &matrix);
	// transform this box by the supplied matrix. It applies the projection part of the matrix
	AABox &transform(const Matrix4x4 &matrix);
	// transform this box by the supplied matrix. It ignores the projection part of the matrix
	AABox &transformAffine(const Matrix4x4 &matrix);
	
	// extends this box to include the given point
	AABox &extend(const Vec3 &p);
	// extends this box to include the given box
	AABox &extend(const AABox &box);

	// return true if p is contained in this box
	bool contains(const Vec3 &p) const;
	// return true if box is *completely* contained in this box
	bool contains(const AABox &box) const;
	
	// return true if box intersect with this box
	bool intersects(const AABox &box) const;

private:
	Vec3 m_min;
	Vec3 m_max;
};

inline AABox::AABox()
{
}

inline AABox::AABox(const Vec3 &min, const Vec3 &max) : m_min(min), m_max(max)
{
}

inline AABox &AABox::setMinMax(const Vec3 &min, const Vec3 &max)
{
	sweglAssertMsg(Vec3::IsAllLessEqual(min, max), "min(%f, %f, %f) - max(%f, %f, %f)", min.x, min.y, min.z, max.x, max.y, max.z);
	m_min = min;
	m_max = max;
	return *this;
}

inline AABox &AABox::setCenterHalfSize(const Vec3 &center, const Vec3 &halfSize)
{
	sweglAssertMsg(Vec3::IsAllGreaterEqual(halfSize, Vec3::Zero), "halfSize(%f, %f, %f)", halfSize.x, halfSize.y, halfSize.z);
	m_min = center - halfSize;
	m_max = center + halfSize;
	return *this;
}

inline AABox::AABox(const AABox &rhs) : m_min(rhs.m_min), m_max(rhs.m_max)
{
}

inline AABox &AABox::operator = (const AABox &rhs)
{
	m_min = rhs.m_min;
	m_max = rhs.m_max;
	return *this;
}

inline Vec3 AABox::center() const
{
	return (m_max + m_min) * 0.5f;
}

inline Vec3 AABox::halfSize() const
{
	return (m_max - m_min) * 0.5f;
}

inline Vec3 AABox::size() const
{
	return m_max - m_min;
}

inline const Vec3 &AABox::minimum() const
{
	return m_min;
}

inline Vec3 &AABox::minimum()
{
	return m_min;
}

inline const Vec3 &AABox::maximum() const
{
	return m_max;
}

inline Vec3 &AABox::maximum()
{
	return m_max;
}

inline AABox &AABox::translate(const Vec3 &translation)
{
	m_min += translation;
	m_max += translation;
	return *this;
}

inline AABox &AABox::rotate(const Matrix3x3 &matrix)
{
	Vec3 hs = halfSize();
	Vec3 newHalfSize(
		Abs(matrix[0][0]) * hs.x + Abs(matrix[1][0]) * hs.y + Abs(matrix[2][0]) * hs.z,
		Abs(matrix[0][1]) * hs.x + Abs(matrix[1][1]) * hs.y + Abs(matrix[2][1]) * hs.z,
		Abs(matrix[0][2]) * hs.x + Abs(matrix[1][2]) * hs.y + Abs(matrix[2][2]) * hs.z);
	setCenterHalfSize(center(), newHalfSize);
	return *this;
}

/*
inline AABox &AABox::transform(const Matrix4x4 &matrix)
{
	shTodo();
	Vec3 newCenter = matrix * center();
	Vec3 hd = halfDiagonal();
	Vec3 newHalfDiagonal(
		Math::Abs(matrix[0][0]) * hd.x + Math::Abs(matrix[1][0]) * hd.y + Math::Abs(matrix[2][0]) * hd.z, 
		Math::Abs(matrix[0][1]) * hd.x + Math::Abs(matrix[1][1]) * hd.y + Math::Abs(matrix[2][1]) * hd.z, 
		Math::Abs(matrix[0][2]) * hd.x + Math::Abs(matrix[1][2]) * hd.y + Math::Abs(matrix[2][2]) * hd.z);
	setCenterExtents(newCenter, newHalfDiagonal);
	return *this;
}
 * 
 * AABox &transformAffine(const Matrix4x4 &matrix);
*/

inline AABox &AABox::extend(const Vec3 &p)
{
	m_min = Vec3::Minimize(m_min, p);
	m_max = Vec3::Maximize(m_max, p);
	return *this;
}

inline AABox &AABox::extend(const AABox &box)
{
	m_min = Vec3::Minimize(m_min, box.m_min);
	m_max = Vec3::Maximize(m_max, box.m_max);
	return *this;
}

inline bool AABox::contains(const Vec3 &p) const
{
	return Vec3::IsAllLessEqual(m_min, p) && Vec3::IsAllGreaterEqual(m_max, p);
}

inline bool AABox::contains(const AABox &box) const
{
	return Vec3::IsAllLessEqual(m_min, box.m_min) && Vec3::IsAllGreaterEqual(m_max, box.m_max);
}

inline bool AABox::intersects(const AABox &box) const
{
	return ! Vec3::IsAnyLess(m_max, box.m_min) && ! Vec3::IsAnyGreater(m_min, box.m_max);
}

}

#endif

